package com.nononsenseapps.feeder.ui.common.htmlview.converter

import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.text.Spannable
import android.text.style.StyleSpan
import androidx.appcompat.content.res.AppCompatResources
import com.nononsenseapps.feeder.ui.R
import com.nononsenseapps.feeder.imageloader.creageAnimatorAbleThumb
import com.nononsenseapps.feeder.imageloader.creageIframeThumb
import com.nononsenseapps.feeder.imageloader.creageVideoThumb
import com.nononsenseapps.feeder.ui.common.htmlview.SensibleSpannableStringBuilder
import io.noties.markwon.span.ClickableIframeSpan
import io.noties.markwon.span.ClickableImageSpan
import io.noties.markwon.span.ClickableVideoSpan
import com.nononsenseapps.feeder.util.LinkProtocolUtil
import com.nononsenseapps.feeder.util.PrefUtils
import com.nononsenseapps.feeder.util.relativeLinkIntoAbsolute
import org.ccil.cowan.tagsoup.Parser
import org.xml.sax.Attributes
import timber.log.Timber
import java.net.URL
import java.util.concurrent.ExecutionException


abstract  class  AbstractLoadImageHtmlConverter (context: Context,
                                               source: String,
                                               protected val siteUrl: URL,
                                               parser: Parser,
                                               maxSize: Point,
                                               protected val allowDownload: Boolean,
                                               val imageCount:Int = 0,
                                               spannableStringBuilder: SensibleSpannableStringBuilder = SensibleSpannableStringBuilder()) :
        HtmlToSpannedConverter(source, siteUrl, parser, context, maxSize, spannableStringBuilder) {

    protected val context: Context = context.applicationContext

    protected val densityScale: Float = this.context.resources.displayMetrics.density
    protected var imageLoaded = 0

    init {
        // Get screen density
    }

    override fun startImg(text: SensibleSpannableStringBuilder,
                          attributes: Attributes) {
        val width: String? = attributes.getValue("", "width")
        val height: String? = attributes.getValue("", "height")

        val srcUrl:String?=attributes.getValue("", "src")

        if (isHtmlAttributeNumberOrPercent(width,2)||isHtmlAttributeNumberOrPercent(height,2)) {
            //super.startImg(text, attributes)
            ignoredImage = true
            return
        }

        val isGif=(srcUrl!=null&&srcUrl.isNotEmpty()&&srcUrl.endsWith(".gif",true))

        val  d: Drawable? = when(isGif){
            true->getImgDrawable(
                    srcUrl,
                    sWidth = parserHtmlAttributeNumberOrPercent(width),
                    sHeight = parserHtmlAttributeNumberOrPercent(height)
            )
            false->getImgDrawable(
                    srcUrl,
                    sWidth = parserHtmlAttributeNumberOrPercent(width),
                    sHeight = parserHtmlAttributeNumberOrPercent(height)
            )
        }
        if (d == null) {
            super.startImg(text, attributes)
            return
        }

        val len = text.length
        text.append("\uFFFC")

        val imgLink = relativeLinkIntoAbsolute(siteUrl, attributes.getValue("", "src"))

        if(isGif) {
            startGifImg(text,d,len,imgLink,d.bounds.width(),d.bounds.height())
        }else{
            text.setSpan(object : ClickableImageSpan(d, imgLink){}, len, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        }

        // Add a line break
        text.append("\n")
        // If there's an alt text, add it in italics
        val alt = attributes.getValue("", "alt")
        if (alt != null && alt.isNotEmpty()&&!alt.endsWith("alt")) {
            val from = text.length
            text.append(alt)
            text.setSpan(StyleSpan(Typeface.ITALIC), from,
                    text.length,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
            text.append("\n")
        }
        imageLoaded++
    }

    open fun startGifImg(text:  SensibleSpannableStringBuilder, thumbnail: Drawable, len:Int, imgLink:String, width:Int, height:Int) {
        thumbnail.let {
            creageAnimatorAbleThumb(context, SizedDrawable(it, it.bounds.width(), it.bounds.height()))
            }.let{
                text.setSpan(object : ClickableImageSpan(it, imgLink){}, len, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
            }
    }

    protected fun getGifImgDrawable(src: String?, sWidth: String, sHeight: String): Drawable? {
        var d: Drawable? = null
        try {
            if (src == null || src.isBlank()) {
                return null
            }
            // Image size
            var shrunk = false
            var hasSize = false
            var hasPercentSize = false
            var w = -1
            var h = -1

            // Calculate size first if possible
            if (sWidth.contains("%") && sHeight.contains("%")) {
                hasPercentSize = true
            }
            if(sWidth == "0" || sHeight == "0"){
                hasPercentSize = true
            } else if ( !sWidth.contains("%") && !sHeight.contains("%")) {
                hasSize = true
                w = Integer.parseInt(sWidth)
                h = Integer.parseInt(sHeight)
                // This should be parsed away, but just in case...
                if (w < 10 || h < 10) {
                    return null
                }
                // Scale with screen density
                w = (w * densityScale + 0.5f).toInt()
                h = (h * densityScale + 0.5f).toInt()
                // Shrink if big (used for picasso downloading)
                // Don't resize if small, since it can be scaled
                // directly in drawable bounds. Need to shrink it to
                // save precious memory however.
                if (w > maxSize.x) {
                    val newSize = scaleImage(w, h)
                    w = newSize.x
                    h = newSize.y
                    shrunk = true
                }
            }

            val imgLink = relativeLinkIntoAbsolute(siteUrl, src)


            d= if (shrunk || hasSize) {
                loadImageAsGif(imgLink,w, h)
            } else if (hasPercentSize) {
                loadImageAsGif(imgLink,maxSize.x/2, maxSize.y/2)
            } else {
                loadImageAsGif(imgLink,maxSize.x/2, maxSize.y/2)
            }
        } catch (e: InterruptedException) {
             Timber.e( e.localizedMessage)
        } catch (e: ExecutionException) {
             Timber.e( e.localizedMessage)
        }
        return d
    }


    protected fun getImgDrawable(src: String?, sWidth: String, sHeight: String): Drawable? {
        var d: BitmapDrawable? = null
        try {
            if (src == null || src.isBlank()) {
                return null
            }

            // Image size
            var shrunk = false
            var hasSize = false
            var hasPercentSize = false
            var autoSize = false
            var w = -1
            var h = -1

            // Calculate size first if possible
            if (sWidth.contains("%") &&
                    sHeight.contains("%")) {
                hasPercentSize = true
            }
            if(sWidth == "0" || sHeight == "0"){
                autoSize = true
            } else if ( !sWidth.contains("%") && !sHeight.contains("%")) {
                hasSize = true
                w = Integer.parseInt(sWidth)
                h = Integer.parseInt(sHeight)
                // This should be parsed away, but just in case...
                if (w < 10 || h < 10) {
                    return null
                }
                // Scale with screen density
                w = (w * densityScale + 0.5f).toInt()
                h = (h * densityScale + 0.5f).toInt()
                // Shrink if big (used for picasso downloading)
                // Don't resize if small, since it can be scaled
                // directly in drawable bounds. Need to shrink it to
                // save precious memory however.
                if (w > maxSize.x) {
                    val newSize = scaleImage(w, h)
                    w = newSize.x
                    h = newSize.y
                    shrunk = true
                }
            }

            val imgLink = relativeLinkIntoAbsolute(siteUrl, src)

            val b: Bitmap = if (shrunk || hasSize) {
                loadImageAsBitmap(imgLink,w, h)
            } else if (hasPercentSize) {
                loadImageAsBitmap(imgLink,maxSize.x, maxSize.y)
            } else if (autoSize){
                loadImageAsBitmap(imgLink,maxSize.x, maxSize.y)
            }  else{
                loadImageAsBitmap(imgLink,maxSize.x, maxSize.y)
            }

            if (w == -1) {
                w = b.width
                h = b.height
                // Scale with screen density
                w = (w * densityScale + 0.5f).toInt()
                h = (h * densityScale + 0.5f).toInt()
            }
            // Enlarge if close, or shrink if big
            if (w.toFloat() / maxSize.x.toFloat() > 0.5) {
                val newSize = scaleImage(w, h)
                w = newSize.x
                h = newSize.y
            }

            d = BitmapDrawable(context.resources, b)
            d.setBounds(0, 0, w, h)
        } catch (e: InterruptedException) {
            Timber.e( e.localizedMessage)
        } catch (e: ExecutionException) {
            Timber.e( e.localizedMessage)
        }

        return d
    }


    override fun startVideo(text:  SensibleSpannableStringBuilder,
                            attributes: Attributes) {
        // Parse information
        val srcurl:String?=attributes.getValue("", "src")
        try {
            // Only insert images if a video was found
            srcurl?.let{
               val poster:String?= attributes.getValue("", "poster")
                if(poster!=null){
                    getImgDrawable(poster,
                            sWidth = parserHtmlAttributeNumberOrPercent(attributes.getValue("", "width")),
                            sHeight = parserHtmlAttributeNumberOrPercent(attributes.getValue("", "height")))
                } else {
                    AppCompatResources.getDrawable(context, when (PrefUtils.isNightMode(context)) {
                        true -> R.drawable.placeholder_image_article_night
                        false -> R.drawable.placeholder_image_article_day
                    })?.also {
                        it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
                    }
                }
            }?.let {
                creageVideoThumb(context, SizedDrawable(it, it.bounds.width(), it.bounds.height()))
            }?.let {
                val span = object : ClickableVideoSpan(it,srcurl) {
                    override fun onClick() {}
                }
                val len = text.length
                text.append("\uFFFC")
                text.setSpan(span, len, text.length,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                // Add newline also
                text.append("\n")
                val from = text.length
                text.append(context.getString(R.string.touch_to_play_video))
                text.setSpan(StyleSpan(Typeface.ITALIC), from,
                        text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                text.append("\n\n")
            }
        } catch (e: Throwable) {
            Timber.e("Failed to start Video tag $e.localizedMessage")
        }
    }


    override fun startIframe(text:  SensibleSpannableStringBuilder,
                             attributes: Attributes) {
        // Parse information
        val video: Video? = getVideo(attributes.getValue("", "src"))
        if(null!=video) {
            try {
                // Only insert images if a video was found in the iframe
                video.let {
                    getYoutubeThumb(video)
                }.let {
                    val span = object : ClickableVideoSpan(it,video.link) {
                        override fun onClick() {}
                    }
                    val len = text.length
                    text.append("\uFFFC")
                    text.setSpan(span, len, text.length,
                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    // Add newline also
                    text.append("\n")
                    val from = text.length
                    text.append(context.getString(R.string.touch_to_play_video))
                    text.setSpan(StyleSpan(Typeface.ITALIC), from,
                            text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    text.append("\n\n")
                }
            } catch (e: Throwable) {
                Timber.e("Failed to start iFrame tag %s",e.localizedMessage)
            }
        }else{
            var srcLink=attributes.getValue("", "src")
            srcLink=LinkProtocolUtil.clearNoHeadUrl(srcLink)
            try {
                // Only insert images if a audio was found
                srcLink?.let {
                    AppCompatResources.getDrawable(context, when (PrefUtils.isNightMode(context)) {
                        true -> R.drawable.placeholder_image_article_night
                        false -> R.drawable.placeholder_image_article_day
                    })?.also {
                        it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
                    }
                }?.let {
                    creageIframeThumb(context, SizedDrawable(it, it.bounds.width(), it.bounds.height()))
                }?.let {
                    val span = object : ClickableIframeSpan(it,srcLink) {
                        override fun onClick() {}
                    }
                    val len = text.length
                    text.append("\uFFFC")
                    text.setSpan(span, len, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    // Add newline also
                    text.append("\n")
                    val from = text.length
                    text.append(context.getString(R.string.open_link_in_web_view))
                    text.setSpan(StyleSpan(Typeface.ITALIC), from,
                            text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    text.append("\n\n")
                }
            } catch (e: Throwable) {
                Timber.e("Failed to start iframe tag %s",e.localizedMessage)
            }
        }
    }

    /**
     * @return a drawable of the video thumbnail with a youtube icon overlayed, or a full size
     * youtube icon in case the thumbnail could not be loaded
     */
    override fun getYoutubeThumb(video: Video): Drawable {
        if(video.imageUrl==null)
            return super.getYoutubeThumb(video)
        return try {
            val imgLink = relativeLinkIntoAbsolute(siteUrl, video.imageUrl)
            loadImageAsBitmap(imgLink,maxSize.x, maxSize.y)
        } catch (e: Throwable) {
            null
        }?.let {
            SizedDrawable(
                    drawable = BitmapDrawable(context.resources, it),
                    width = it.width,
                    height = it.height
            )
        }?.let { sizedDrawable ->
            creageVideoThumb(context, sizedDrawable)
        }.or {
            super.getYoutubeThumb(video)
        }
    }

    abstract  fun loadImageAsDrawable(urlLink: String, width: Int, height: Int): Drawable

    abstract  fun loadImageAsBitmap(urlLink:String,width: Int,height: Int):Bitmap

    abstract  fun loadImageAsGif(urlLink:String,width: Int,height: Int):Drawable
}


fun Drawable?.or(callee: () -> Drawable) =
        this ?: callee()

data class SizedDrawable(
        val drawable: Drawable,
        val width: Int,
        val height: Int
)